#include "StdAfx.h"

#include <MyControls/PEditor.h>

namespace My {

const int MinColumnWidth	= 8;
const int MinInfoHeight		= 19;
const int SplitterSize		= 5;

#include <MyFC/Utility.h>
#include <algorithm>
#include <xtd/functional>

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

PEditor::PEditor(String const & RegistrySection) :
	m_List(* this),
	m_Info(* this),
	m_VSplitter(* this),
	m_HSplitter(* this),
	m_RegistrySection(RegistrySection)
{
	m_pSavedActive = 0;

	m_ButtonUp.Load("BUTTONUP", "BUTTONUPMASK");
	m_ButtonDown.Load("BUTTONDOWN", "BUTTONDOWNMASK");

	m_CaptionWidth = AfxGetApp()->GetProfileInt(m_RegistrySection.c_str(), "Caption", 125);
	m_InfoHeight = 	AfxGetApp()->GetProfileInt(m_RegistrySection.c_str(), "Info", 75);
	m_InfoVisible = true;
}

PEditor::~PEditor()
{
	Close();
	AfxGetApp()->WriteProfileInt(m_RegistrySection.c_str(), "Caption", m_CaptionWidth);
	AfxGetApp()->WriteProfileInt(m_RegistrySection.c_str(), "Info", m_InfoHeight);
}

int PEditor::GetCaptionWidth() const
{
	int w = m_CaptionWidth;
	if (w > m_List.ClientWidth() - MinColumnWidth)
		w = m_List.ClientWidth() - MinColumnWidth;
	return w;
}

void PEditor::SetCaptionWidth(int w)
{
	if(w < MinColumnWidth)
		w = MinColumnWidth;
	if(w != m_CaptionWidth)
	{
		m_CaptionWidth = w;
		m_List.UpdateBounds();
		m_List.Invalidate();
	}
}

int PEditor::GetInfoHeight() const
{
	if (!m_InfoVisible)
		return 0;
	int h = m_InfoHeight;
	if (h > ClientHeight() - MinInfoHeight)
		h = ClientHeight() - MinInfoHeight;
	return h;
}

void PEditor::SetInfoHeight(int h)
{
	if (h < MinInfoHeight)
		h = MinInfoHeight;
	if(h != m_InfoHeight)
	{
		m_InfoHeight = h;
		m_List.UpdateBounds();
		m_Info.UpdateBounds();
	}
}

bool PEditor::IsEmpty() const
{
	return m_Objects.empty();
}

void PEditor::Open(EditableObject * pObject)
{
	if (!IsEmpty())
		Close();
	if (pObject == 0)
		return;

	m_Objects.push_back(pObject);
//	pObject->EditableEvents().Attach(this);

	OpenProperties(pObject->GetProperties());
}

void PEditor::Open(const EditableList & list)
{
	if (!IsEmpty())
		Close();
	if (list.empty())
		return;
	// Determine map of common properties
	EditableList::const_iterator it = list.begin();
	assert(* it != 0);
	PropertyMap map1 = (* it)->GetProperties();
	for (; it != list.end(); ++it) {		
		EditableObject * pObject = * it;
		assert(pObject != 0);
		const PropertyMap & map2 = pObject->GetProperties();
		
		PropertyMap::iterator pit1;
		for (pit1 = map1.begin(); pit1 != map1.end(); ) {
			PropertyMap::const_iterator pit2 = map2.find((* pit1).first);
			if (pit2 == map2.end() || (* pit2).second != (* pit1).second)
				map1.erase(pit1++);
			else
				++pit1;
		}
	}

	if (map1.empty())
		return;

	m_Objects = list;
//	for (it = m_Objects.begin(); it != m_Objects.end(); ++it)
//		(* it)->EditableEvents().Attach(this);

	OpenProperties(map1);
}

void PEditor::OpenProperties(const PropertyMap & map)
{
	assert(m_Rows.empty());

	m_Rows.reserve(map.size());
	PropertyMap::const_iterator it;
	for (it = map.begin(); it != map.end(); ++it) {
		m_Rows.push_back(
			new PropertyRow(* this, (* it).first, (* it).second, m_Objects)
		);
	}

	assert(m_List.IsEmpty());
	m_List.InsertRows(0, &m_Rows[0], m_Rows.size());

	My::Size i;
	for (i = 0; i < m_Rows.size(); ++i) 
	{
		PropertyRow * pRow = static_cast<PropertyRow *>(m_Rows[i]);
		bool expanded = pRow->GetProperty()->DefaultExpanded();
		StateMap::iterator it = m_StateMap.find(pRow->GetProperty());
		if (it != m_StateMap.end())
			expanded = (* it).second;
		if (expanded)
			pRow->Expand();
	}

	for (i = 0; i < m_List.GetRowCount(); ++i) {
		PropertyRow * pRow = static_cast<PropertyRow *>(m_List.GetRow(i));
		if (pRow->GetProperty() == m_pSavedActive) {
			m_List.SetActiveRow(pRow->GetRowIndex());
			break;
		}
	}
	m_pSavedActive = 0;

}

void PEditor::Update(PropertyBase * pProperty)
{
	if (pProperty != 0) {
		for (My::Size i = 0; i < m_List.GetRowCount(); ++i) {
			PropertyRow * pRow = static_cast<PropertyRow *>(m_List.GetRow(i));
			if (pRow->GetProperty() == pProperty) {
				pRow->Update();
				break;
			}
		}
	} else {
		ProcessEvent(UpdateUIEvent());
		m_List.Invalidate();
		m_Info.Invalidate();
	}
}

void PEditor::InvalidateInfo()
{
	m_Info.Invalidate();
}

void PEditor::Close()
{			
	if (m_List.GetActiveRow() != -1)
		m_pSavedActive = m_List.GetActiveRowPtr()->GetProperty();

	My::Size i;
	for (i = 0; i < m_Rows.size(); ++i) 
	{
		PropertyRow * pRow = static_cast<PropertyRow *>(m_Rows[i]);
		m_StateMap[pRow->GetProperty()] = pRow->IsExpanded();
		pRow->Collapse();
	}

	m_List.Clear();

	std::for_each(m_Rows.begin(), m_Rows.end(),	xtd::deleter());
	m_Rows.clear();

//	for (EditableList::const_iterator it = m_Objects.begin(); it != m_Objects.end(); ++it)
//		(* it)->EditableEvents().Detach(this);

	m_Objects.clear();
}

void PEditor::ShowInfo(bool on)
{
	if (on != m_InfoVisible) {
		m_InfoVisible = on;
		LayoutControls();
	}
}

void PEditor::ProcessEvent(Event & e)
{
//	if (e.Is<EditableObject::PropertyChange>()) {
//		Update(e.As<EditableObject::PropertyChange>().m_pProperty);
//	} else
		Container::ProcessEvent(e);
}

void PEditor::UpdateBounds()
{
	Container::UpdateBounds();
	m_List.UpdateBounds();
	UpdateCaptionWidth();
	UpdateInfoHeight();
}

void PEditor::HandlePaint(CDC * pDC, const CRect & rect)
{
	CBrush bFace(GetSysColor(COLOR_BTNFACE));
	pDC->FillRect(&rect, &bFace);
}

bool PEditor::HandleCursor(unsigned int flags, const CPoint & p)
{
	::SetCursor(::LoadCursor(NULL, IDC_ARROW));
	return true;
}

// PEditor::VSplitter

PEditor::VSplitter::VSplitter(PEditor & e) :
	m_Editor(e), m_List(e.m_List)
{
	m_List.Add(this);
}

CRect PEditor::VSplitter::Bounds() const
{
	CRect r	= m_List.ClientRect();
	r.left = m_Editor.GetCaptionWidth() - SplitterSize / 2;
	r.right	= r.left + SplitterSize;
	return r;
}

bool PEditor::VSplitter::HandleDragStart(unsigned int flags, const CPoint & p0, const CPoint & p)
{
	m_Pos =	ClientToParent(p0).x;
	m_Start	= m_Editor.GetCaptionWidth();
	return true;
}

void PEditor::VSplitter::HandleDragEnd(unsigned int flags, const CPoint & p)
{
}

void PEditor::VSplitter::HandleDragging(unsigned int flags, const CPoint & p)
{
	int	x =	ClientToParent(p).x;
	m_Editor.SetCaptionWidth(m_Start + x - m_Pos);
}

bool PEditor::VSplitter::HandleCursor(unsigned int flags, const CPoint & p)
{
	SetCursor(::LoadCursor(NULL,IDC_SIZEWE));
	return true;
}

// PEditor::HSplitter

PEditor::HSplitter::HSplitter(PEditor & e) :
	m_Editor(e)
{
	m_Editor.Add(this);
}

CRect PEditor::HSplitter::Bounds() const
{
	CRect r	= m_Editor.ClientRect();
	r.top = r.bottom - m_Editor.GetInfoHeight() - SplitterSize / 2;
	r.bottom = r.top + SplitterSize;
	return r;
}

bool PEditor::HSplitter::HandleDragStart(unsigned int flags, const CPoint & p0, const CPoint & p)
{
	m_Pos =	ClientToParent(p0).y;
	m_Start	= m_Editor.GetInfoHeight();
	return true;
}

void PEditor::HSplitter::HandleDragEnd(unsigned int flags, const CPoint & p)
{
}

void PEditor::HSplitter::HandleDragging(unsigned int flags, const CPoint & p)
{
	int	y =	ClientToParent(p).y;
	m_Editor.SetInfoHeight(m_Start - y + m_Pos);
}

bool PEditor::HSplitter::HandleCursor(unsigned int flags, const CPoint & p)
{
	SetCursor(::LoadCursor(NULL, IDC_SIZENS));
	return true;
}

// PEditor::InfoPanel

PEditor::InfoPanel::InfoPanel(PEditor & e) : m_Editor(e)
{
	m_Font.CreateFont(-11, 0, 0, 0, 400, FALSE, FALSE, 0,  DEFAULT_CHARSET, 0, 0, 0, 0, "Tahoma");

	m_Editor.Add(this);
	m_Glyph.Load("INFO");
}

CRect PEditor::InfoPanel::Bounds() const
{
	CRect r = m_Editor.ClientRect();
	r.top = r.bottom - m_Editor.GetInfoHeight();
	return r;
}

bool PEditor::InfoPanel::Visible() const
{
	return m_Editor.m_InfoVisible;
}

const CFont * PEditor::InfoPanel::Font() const
{
	return &m_Font;
}

void PEditor::InfoPanel::HandlePaint(CDC * pDC, const CRect & r)
{
	My::SaveObjects save(pDC);

	CRect rClient = ClientRect();

	COLORREF cF = ::GetSysColor(COLOR_BTNFACE);
	COLORREF cS = ::GetSysColor(COLOR_BTNSHADOW);
	COLORREF cText = ::GetSysColor(COLOR_WINDOWTEXT);

	CBrush bFace(cF);
	pDC->FillRect(&r, &bFace);

	CPen pShadow(PS_SOLID, 1, cS);
	pDC->SelectObject(&pShadow);

	pDC->MoveTo(rClient.left, rClient.top);
	pDC->LineTo(rClient.right, rClient.top);

	int index = m_Editor.m_List.GetActiveRow();
	if (index != -1) {

		PropertyRow * pRow = static_cast<PropertyRow *>(m_Editor.m_List.GetRow(index));
		String hint = pRow->GetProperty()->GetHint(m_Editor.m_Objects);

		if (!hint.empty()) {

			rClient.InflateRect(-4, -2);

			m_Glyph.Draw(pDC, rClient.left, rClient.top);
			rClient.InflateRect(- m_Glyph.Width() - 4, 0, -2, 0);

			pDC->SetBkMode(TRANSPARENT);
			pDC->SetTextColor(cText);

			UINT nFormat;
			if (m_Editor.m_InfoHeight == MinInfoHeight)
				nFormat = DT_LEFT|DT_VCENTER|DT_SINGLELINE|DT_END_ELLIPSIS;
			else
				nFormat = DT_LEFT|DT_TOP|DT_WORDBREAK|DT_WORD_ELLIPSIS;

			pDC->DrawText(hint.c_str(), &rClient, nFormat);
		}
	}
}

} // My